#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/select.h>

#define LISTEN_NUM 10

void error_handing(char *message)
{
    fputs(message,stderr);
    fputc('\n',stderr);
    exit(1);
}
int main(int argc, char const *argv[])
{
    //步骤1：创建套接字
    int server_sock;
    server_sock = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
    if (server_sock == -1)
    {
        error_handing("socket() error");
    }
    
    //步骤2：绑定监听端口
    struct sockaddr_in server_addr;
    memset(&server_addr,0,sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(atoi("3333"));
    if (bind(server_sock,(struct sockaddr *)&server_addr,sizeof(server_addr)) == -1)
    {
        error_handing("bind() error");
    }

    //步骤3：开启监听
    if (listen(server_sock,LISTEN_NUM) == -1)
    {
        error_handing("listen() error");
    }

    //步骤4：做select调用之前的准备

    //步骤4.1：初始化sock 集合
    fd_set fd_reads,fd_copy;//经过select函数之后，需要比较fd_set中的位是否改变，所以这里总是需要两个fd_set变量
    FD_ZERO(&fd_reads);
    
    //步骤4.2：将server_sock加入到集合中
    FD_SET(server_sock,&fd_reads);

    //步骤4.3：初始化最大值
    int fd_max = server_sock;//定义最大的socket
    while (1)
    {
        fd_copy = fd_reads;

        //步骤4.4：设置超时时间
        struct timeval time_out;
        time_out.tv_sec = 5;
        
        //步骤5：调用select函数，讨论其返回值
        int activity_sockets = select(fd_max + 1,&fd_copy,NULL,NULL,&time_out);

        if (activity_sockets == -1)//发生错误，退出
        {
            error_handing("select() error");
        }
        else if (activity_sockets == 0)//超时
        {
            //选择继续运行
            printf("Time out.\n");
            continue;
        }

        for (size_t i = 0; i < fd_max + 1; i++)//比较是否对应的事件位是否改变
        {
            if (FD_ISSET(i,&fd_copy))//说明i事件活动了
            {   
                if(i == server_sock)//说明有新的客户端接入
                {
                    printf("New client connected...\n");
                    struct sockaddr client_addr;
                    int sizeof_client_addr = sizeof(client_addr);
                    int client_sock = accept(server_sock,&client_addr,&sizeof_client_addr);

                    //将客户端的socket加入到总的fd_set中，就可以使用select查询处理
                    FD_SET(client_sock,&fd_reads);
                    
                    //更新最大值，因为select监听的sockt是从0~fd_max  
                    fd_max = (client_sock > fd_max) ? client_sock : fd_max;
                }
                else//说明有客户端数据进来
                {
                    const int message_size = 30;
                    char message[message_size];

                    int read_size;
                    read_size = read(i,message,message_size);

                    if (read_size == 0)//说明客户端请求关闭连接
                    {
                        printf("Close client...\n");
                        FD_CLR(i,&fd_reads);//踢出监控集合
                        close(i);//再关闭连接
                    }
                    else//收到数据，选择给它回发过去
                    {
                        message[read_size] = '\0';
                        printf("Receive message is %s.\n",message);
                        write(i,message,read_size);
                    }
                }
            } 
        }  
    }

    close(server_sock);
    return 0;
}
